<?php
/**
 * @package Curly
 * @subpackage Collections
 * @version 0.9
 * @link http://curly.codeplex.com/
 * @license http://curly.codeplex.com/license The MIT License
 * @author Dawid Zawada
 */

namespace Curly\Collections
{
    /**
     * Represents a collection of keys and values
     * 
 	 * @package Curly
 	 * @subpackage Collections
     * @license http://curly.codeplex.com/license The MIT License
     */
    class Collection implements \Countable, \IteratorAggregate
    {
        /**
         * Container for key-value pairs
         * 
         * @var array
         */
        protected $items = array();
        
        /**
         * Returns the value associated with the given key or
         * null if not found
         * 
         * The key must be of scalar type (just as array keys)
         * 
         * @param string $key
         * @return mixed
         * @throws \InvalidArgumentException
         */
        public function get( $key )
        {
            $this->validateKey( $key );
            
            if( isset( $this->items[$key] ) )
            {
                return $this->items[$key];
            }
            else
            {
                return null;
            }
        }
        
        /**
         * Returns all items (key-value pairs) of this collection
         *
         * @return array
         */
        public function getAll()
        {
            return $this->items;
        }
        
    	/**
         * Returns all keys of this collection
         *
         * @return array
         */
        public function getKeys()
        {
            return array_keys( $this->items );
        }
        
    	/**
         * Returns all values of this collection
         *
         * @return array
         */
        public function getValues()
        {
            return array_values( $this->items );
        }
        
    	/**
         * Adds or updates the value associated with the given key
         * 
         * The key must be of scalar type (just as array keys).
         * The method returns $this object, so you can do
         * method chaining.
         * 
         * @param mixed $key
         * @param mixed $value
         * @return Curly\Collections\Collection
         * @throws \InvalidArgumentException
         */
        public function set( $key, $value )
        {
            $this->validateKey( $key );
            
            $this->items[$key] = $value;
            return $this;
        }
        
    	/**
         * Merges this collection with the given array
         * 
         * If the array has a key that already exists 
         * in this collecion, the value associated with that key
         * will overwrite the one stored in this collection.
         * The method returns $this object, so you can do
         * method chaining.
         * 
         * @param array $items
         * @return Curly\Collections\Collection
         * @throws \UnexpectedValueException
         */
        public function setMultiple( array $items )
        {
            // Don't remove the try-catch block, it's useful
            // when the validateKey method is overridden
            try
            {
                foreach( $items as $key => $value )
                {
                    $this->set( $key, $value );
                }
            }
            catch( \InvalidArgumentException $e )
            {
                throw new \UnexpectedValueException( "At least one key of the given \$items array has an invalid type", null, $e );
            }
            
            return $this;
        }
        
    	/**
         * Removes the element with the given key from this collection
         * 
         * The key must be of scalar type (just as array keys).
         * This method returns true if the element is successfully found 
         * and removed, false otherwise
         * 
         * @param mixed $key
         * @return bool
         * @throws \InvalidArgumentException
         */
        public function remove( $key )
        {
            $this->validateKey( $key );
            
            if( isset( $this->items[$key] ) )
            {
                unset( $this->items[$key] );
                return true;
            }
            else
            {
                return false;
            }
        }
        
     	/**
         * Removes all keys and values from this collection
         */
        public function clear()
        {
            $this->items = array();
        }
        
    	/**
         * Determines whether this collection contains the given key
         * 
         * The key must be of scalar type (just as array keys).
         * 
         * @param mixed $key
         * @return bool
         */
        public function containsKey( $key )
        {
            $this->validateKey( $key );
            
            return isset( $this->items[$key] );
        }
        
        /**
         * Determines whether this collection contains the given value
         * 
         * @param mixed $value
         * @return bool
         */
        public function containsValue( $value )
        {
            return array_search( $value, $this->items ) !== false;
        }
        
    	/**
         * Throws an exception if the given key is not of scalar type
         * 
         * @see http://www.php.net/manual/en/function.is-scalar.php
         * @param mixed $key
         * @throws \InvalidArgumentException
         */
        protected function validateKey( $key )
        {
            if( !is_scalar( $key ) )
            {
                throw new \InvalidArgumentException( "Key must be of a scalar type, '" . gettype( $key ) . "' given" );
            }
        }
        
        /**
         * Returns a number of items stored in this collection
         *  
         * @return int
         */
        public function count()
        {
            return count( $this->items );
        }
        
        /**
         * Returns an iterator for this collection
         * 
         * @return \Traversable
         */
        public function getIterator()
        {
            return new \ArrayIterator( $this->items );
        }
    }
}